package discard;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import hyl.core.MyFun;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
@SuppressWarnings("unused")
public class Http3 {
	private static final Logger LOG = LoggerFactory.getLogger(Http3.class);
	public static final String CS_UTF8 = "UTF-8";
	private static final int TIME_OUT = 10; // 超时参数

	private static final String KEY_STORE_TYPE_BKS = "bks";// 证书类型
	private static final String KEY_STORE_TYPE_P12 = "PKCS12";// 证书类型
	private static final String KEY_STORE_PASSWORD = "***";// 证书密码（应该是客户端证书密码）
	private static final String KEY_STORE_TRUST_PASSWORD = "***";// 授信证书密码（应该是服务端证书密码）
	private static final int cacheSize = 10 * 1024 * 1024;
	public static final MediaType MEDIA_JSON = MediaType.parse("application/json; charset=" + CS_UTF8);
	public static final MediaType MEDIA_MULTI = MediaType.parse("multipart/form-data");
	public static final MediaType MEDIA_FILE = MediaType.parse("text/x-markdown; charset=utf-8");
	public static final MediaType MEDIA_FORM = MediaType.parse("application/x-www-form-urlencoded; charset=" + CS_UTF8);
	private okhttp3.Request.Builder _builder;
	private MediaType currentMediatype = null;
	private static String DEFCOOKIE = "CNHYLSESSION";
	private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
	CookieJar _cookieJar = new CookieJar() {
		@Override
		// 保存到cookie
		public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
			cookieStore.put(httpUrl.host(), list);
		}
		@Override
		// 从cookie中重新载入
		public List<Cookie> loadForRequest(HttpUrl httpUrl) {
			List<Cookie> cookies = cookieStore.get(httpUrl.host());
			return cookies != null ? cookies : new ArrayList<Cookie>();
		}
	};
	private OkHttpClient.Builder _http = null;
	// 初始化client的配置
	/**
	 * @title: Http3 @description: @param isredirect 是否重定向 @param cookie
	 *         cookie完整内容 @param cookieJar cookie保存和载入事件 @param certs
	 *         验证服务端的数字证书 @throws
	 */
	public Http3() {
		config(null, null);
	}
	public static String getOnce(String url) {
		OkHttpClient client = new OkHttpClient();
		Request request = new Request.Builder().url(url).get().build();
		Response response;
		try {
			response = client.newCall(request).execute();
			String msg = response.body().string();
			return msg;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	public Http3 config(Boolean isredirect, CookieJar cookieJar) {
		_http = new OkHttpClient.Builder();// 构建器
		_http.connectTimeout(TIME_OUT, TimeUnit.SECONDS) // 连接超时
				.writeTimeout(TIME_OUT, TimeUnit.SECONDS) // 写入超时
				.readTimeout(TIME_OUT, TimeUnit.SECONDS);// 读取超时
		// okhttp可以缓存数据....指定缓存路径
		// File sdcache = new File(Environment.getExternalStorageDirectory(), "cache");
		// .addInterceptor(new CommonParamsInterceptor()) //添加的是应用拦截器...公共参数
		// .addNetworkInterceptor(new CacheInterceptor())//添加的网络拦截器
		// .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize))//设置缓存 // 指定缓存大小
		if (isredirect != null && isredirect) // 允许重定向
			_http.followRedirects(true);
		if (cookieJar == null)
			_http.cookieJar(_cookieJar);
		else
			_http.cookieJar(cookieJar);
		// 导入数字证书
		// build.hostnameVerifier(new HostnameVerifier() {
		// @Override
		// public boolean verify(String arg0, SSLSession arg1) {
		// return true;
		// }
		// });
		return this;
	}
	public Http3 loadCerts(InputStream... certs) {
		if (certs == null || certs.length == 0) {// 不做验证
			_http.socketFactory(SSLFactory.getTrustAllSSLFactory(new TrustAllManager()));
			_http.hostnameVerifier(SSLFactory.getTrustAllHostnameVerifier());
		} else {// 对证书进行校验
			_http.socketFactory(SSLFactory.getSSLSocketFactory(certs));
		}
		return this;
	}
	private okhttp3.Request.Builder getRequest() {
		if (_builder == null)
			_builder = new Request.Builder();
		return _builder;
	}
	/**
	 * 
	 * <p>
	 * Title: url
	 * </p>
	 * <p>
	 * Description: 设置url
	 * </p>
	 * 
	 * @param url
	 * @return @throws IOException @return :MyHttp @throws
	 */
	public Http3 url(String url) throws IOException {
		if (MyFun.isEmpty(url))
			throw new IOException("url() error :url is null");
		getRequest().url(url).header("Zdy", MyFun.getUUID());
		return this;
	}
	/**
	 * 
	 * <p>
	 * Title: iniHead
	 * </p>
	 * <p>
	 * Description: 构造头部 例如 iniHead("cookie",
	 * "JSESSIONID=EB36DE5E50E342D86C55DAE0CDDD4F6D")
	 * </p>
	 * 
	 * @param name
	 * @param value
	 * @return @throws IOException @return :MyHttp @throws
	 */
	public Http3 addHead(Map<String, String> headmap) throws IOException {
		int i = 0;
		if (headmap == null)
			return this;
		for (Entry<String, String> en : headmap.entrySet()) {
			if (i == 0)
				getRequest().header(en.getKey(), en.getValue());
			else
				getRequest().addHeader(en.getKey(), en.getValue());
			i++;
		}
		return this;
	}
	public Http3 addHead(String name, String val) {
		getRequest().addHeader(name, val);
		return this;
	}
	// 以reponse方式返回
	/**
	 * 
	 * <p>
	 * Title: getReponse
	 * </p>
	 * <p>
	 * Description: 以response方式返回结果 try { Response response =http. getReponse() ; if
	 * (response.isSuccessful()) { String json = response.body().string();
	 * System.out.println(json); String post =
	 * JSON.parseObject(json).getString("postBody"); System.out.println("转义之前：" +
	 * post); System.out.println("转义之后：" + URLDecoder.decode(post)); } } catch
	 * (IOException e) { e.printStackTrace(); } @return @throws IOException @return
	 * :Response @throws
	 */
	public Response exec() throws IOException {
		Request request = getRequest().build();
		return _http.build().newCall(request).execute();
	}
	/**
	 * 
	 * <p>
	 * Title: getResult
	 * </p>
	 * <p>
	 * Description: 以json或text字符串方式返回结果
	 * </p>
	 * 
	 * @return @throws IOException @return :String @throws
	 */
	public String execWithResult() throws IOException {
		Request request = getRequest().build();
		Response response = _http.build().newCall(request).execute();
		if (response.isSuccessful()) {
			return response.body().string();
		} else {
			throw new IOException("req() error: " + response);
		}
	}
	/**
	 * 
	 * <p>
	 * Title: call
	 * </p>
	 * <p>
	 * Description: 以异步方式处理 返回结果 类似于ajax
	 * </p>
	 * 
	 * @param callback
	 * @throws IOException
	 * @return :void @throws
	 */
	public void execWithCall(final Callback callback) throws IOException {
		Callback cb;
		if (callback != null)
			cb = callback;
		else
			cb = new Callback() {
				@Override
				public void onFailure(Call call, IOException e) {
					// TODO Auto-generated method stub
					// System.out.println();
				}
				@Override
				public void onResponse(Call call, Response response) throws IOException {
					if (response.isSuccessful()) {
						Headers headers = response.headers();
						List<String> cookies = headers.values("Set-Cookie");
						LOG.debug("onResponse: " + cookies.size());
						for (String str : cookies) {
							if (str.startsWith(DEFCOOKIE)) {
								// 将sessionId保存到本地
								LOG.debug("onResponse: " + str.split(";")[0]);
							}
						}
					}
				}
			};
		Request request = getRequest().build();
		_http.build().newCall(request).enqueue(cb);
	}
	/**
	 * 
	 * <p>
	 * Title: postMap
	 * </p>
	 * <p>
	 * Description: 提交map参数 postJson/postFiles 和postmap使用时任选一种
	 * </p>
	 * 
	 * @param params
	 * @return @throws IOException @return :MyHttp @throws
	 */
	public Http3 postMap(Map<String, String> params) throws IOException {
		if (currentMediatype != null) {
			return this;
		}
		if (params == null || params.isEmpty())
			return this;
		FormBody.Builder builder = new FormBody.Builder(Charset.forName(CS_UTF8));
		// 遍历集合
		for (String key : params.keySet()) {
			builder.add(key, params.get(key));
		}
		currentMediatype = MEDIA_FORM;
		RequestBody requestBody = builder.build();
		getRequest().post(requestBody);
		return this;
	}
	/**
	 * 
	 * <p>
	 * Title: postJson
	 * </p>
	 * <p>
	 * Description:提交json格式的请求参数 postJson/postFiles 和postmap使用时任选一种
	 * </p>
	 * 
	 * @param json
	 * @return @throws IOException @return :MyHttp @throws
	 */
	public Http3 postJson(String json) throws IOException {
		if (currentMediatype != null || MyFun.isEmpty(json)) {
			return this;
		}
		currentMediatype = MEDIA_JSON;
		RequestBody body = RequestBody.create(MEDIA_JSON, json);
		getRequest().post(body);
		return this;
	}
	// MediaType filetype=MediaType.parse("image/png")
	// MediaType.parse("application/octet-stream")
	/**
	 * 
	 * <p>
	 * Title: postFiles
	 * </p>
	 * <p>
	 * Description: 上传文件 使用postFiles 方法以后不能再用其他post的方法 会有冲突 不能与 postjson 和postmap
	 * 同时使用
	 * </p>
	 * 
	 * @param maps
	 * @param files
	 * @return @throws IOException @return :MyHttp @throws
	 */
	public Http3 postFiles(Map<String, Object> maps, List<MultiFile> files) throws IOException {
		if (currentMediatype != null)
			return this;
		MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
		if (maps != null) {
			for (String key : maps.keySet()) {
				builder.addFormDataPart(key, maps.get(key).toString());
			}
		}
		if (files != null) {
			for (MultiFile mf : files) {
				Headers head = Headers.of("Content-Disposition", mf.getHeadItem());
				RequestBody body = RequestBody.create(mf.getFileType(), mf.getFile());
				builder.addPart(head, body);
			}
		}
		currentMediatype = MEDIA_MULTI;
		MultipartBody multiform = builder.build();
		getRequest().post(multiform);
		return this;
	}
	public Http3 postFile(File file) {
		if (currentMediatype != null || file == null)
			return this;
		currentMediatype = MEDIA_FILE;
		getRequest().put(RequestBody.create(MEDIA_FILE, file));
		return this;
	}
	public Http3 postStream() throws IOException {
		if (currentMediatype != null)
			return this;
		RequestBody requestBody = new RequestBody() {
			@Override
			public MediaType contentType() {
				return MEDIA_FILE;
			}
			@Override
			public void writeTo(BufferedSink sink) throws IOException {
				sink.writeUtf8("Numbers\n");
				sink.writeUtf8("-------\n");
				for (int i = 2; i <= 997; i++) {
					sink.writeUtf8(String.format(" * %s \n", i));
				}
			}
		};
		currentMediatype = MEDIA_FILE;
		getRequest().post(requestBody);
		return this;
	}
	/**
	 * @param url        下载链接
	 * @param startIndex 下载起始位置
	 * @param endIndex   结束为止
	 * @param callback   回调
	 * @throws IOException
	 */
	public Http3 downloadFileByRange(String url, long startIndex, long endIndex, Callback callback) throws IOException {
		// 创建一个Request
		// header("RANGE", "bytes=" + startIndex + "-" +
		// endIndex)，在OkHttp请求头中添加RANGE（范围）参数，告诉服务器需要下载文件内容的始末位置。鉴于OkHttp的火热程度，好像人人都会使用OkHttp，我就不赘言了。
		// 设置分段下载的头信息。 Range:做分段数据请求,断点续传指示下载的区间。格式: Range bytes=0-1024或者bytes:0-1024
		addHead("RANGE", "bytes=" + startIndex + "-" + endIndex);
		return this;
	}
}
class TrustAllManager implements X509TrustManager {
	@Override
	public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
	}
	@Override
	public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
	}
	@Override
	public X509Certificate[] getAcceptedIssuers() {
		return new X509Certificate[0];
	}
}
// ssl相关操作
class SSLFactory {
	static HostnameVerifier getTrustAllHostnameVerifier() {
		return new HostnameVerifier() {
			@Override
			public boolean verify(String hostname, SSLSession session) {
				return true;
			}
		};
	}
	// 默认信任所有证书
	static SSLSocketFactory getTrustAllSSLFactory(TrustAllManager trustAllManager) {
		SSLSocketFactory ssfFactory = null;
		try {
			SSLContext sc = SSLContext.getInstance("TLS");
			sc.init(null, new TrustManager[] { trustAllManager }, new SecureRandom());
			ssfFactory = sc.getSocketFactory();
		} catch (Exception ignored) {
			ignored.printStackTrace();
		}
		return ssfFactory;
	}
	/**
	 * 信任指定证书
	 *
	 * @param certificates 证书流文件
	 * @return
	 */
	static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
		try {
			CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
			KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
			keyStore.load(null);
			int index = 0;
			for (InputStream certificate : certificates) {
				try {
					if (certificate != null) {
						certificate.close();
						continue;
					}
				} catch (IOException e) {
				}
				String certificateAlias = Integer.toString(index++);
				keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
			}
			SSLContext sslContext = SSLContext.getInstance("TLS");
			TrustManagerFactory trustManagerFactory = TrustManagerFactory
					.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			trustManagerFactory.init(keyStore);
			sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
			return sslContext.getSocketFactory();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}
class MultiFile {
	public MultiFile(String inputname, String filename, MediaType type, File file) {
		super();
		this.type = type;
		this.file = file;
		this.inputname = inputname;
		this.filename = filename;
	}
	private MediaType type = MediaType.parse("application/octet-stream");
	private File file;
	private String inputname = "file";
	private String filename = "newfile.jpg";
	public String getHeadItem() {
		return "form-data; name=\"" + inputname + "\";filename=\"" + filename + "\"";
	}
	public MediaType getFileType() {
		return type;
	}
	public File getFile() {
		return file;
	}
}

